const NOOP = () => { }

type CancelFn = () => void
type AsyncFunction<T extends any[], R> = (...args: T) => Promise<R>

interface CanCancelResult<T extends any[], R> {
  cancel: () => void
  run: (...args: T) => Promise<R>
}

export function createCanCancelFn<T extends any[], R>(
  fn: AsyncFunction<T, R>
): CanCancelResult<T, R> {
  let cancel: CancelFn = NOOP

  return {
    cancel: () => cancel(),
    run: (...args: T): Promise<R> => {
      return new Promise<R>((resolve, reject) => {
        cancel()
        cancel = () => {
          resolve = reject = NOOP
        }
        fn(...args).then(
          (res: R) => resolve(res),
          (err: any) => reject(err)
        )
      })
    }
  }
}
